/**
 * This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
 */
package com.softwarecraftsmen.orogeny.application.compilation;

import static com.softwarecraftsmen.orogeny.application.compilation.ClassToResourceUrl.relativeUrlRepresentingAResourceOnTheClassPathToClassName;
import static com.softwarecraftsmen.orogeny.application.compilation.ClassToResourceUrl.isClassResource;
import org.jetbrains.annotations.NotNull;

import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class InMemoryCompiledClassLoader extends URLClassLoader
{
	private final Map<String, byte[]> classNameToBytes;
	private final Set<String> knownClassNames;

	public InMemoryCompiledClassLoader(final @NotNull ClassLoader parentClassLoader, final @NotNull Map<String, byte[]> classNameToBytes)
	{
		super(new URL[0], parentClassLoader);
		this.classNameToBytes = new LinkedHashMap<String, byte[]>(classNameToBytes);
		this.knownClassNames = classNameToBytes.keySet();
	}

	@NotNull
	public Class load(final @NotNull String className) throws ClassNotFoundException
	{
		return loadClass(className);
	}

	@NotNull
	public Iterable<Class> loadAll() throws ClassNotFoundException
	{
		List<Class> classes = new ArrayList<Class>(classNameToBytes.size());
		for (String name : classNameToBytes.keySet())
		{
			classes.add(loadClass(name));
		}
		return classes;
	}

	@Override
	protected Class findClass(final @NotNull String className) throws ClassNotFoundException
	{
		if (classNameToBytes.containsKey(className))
		{
			byte[] bytesForClass = classNameToBytes.get(className);
			final Class<?> aClass = defineClass(className, bytesForClass, 0, bytesForClass.length);
			clearAsNowDefined(className);
			return aClass;
		}
		else
		{
			return super.findClass(className);
		}
	}

	private void clearAsNowDefined(final String className)
	{
		classNameToBytes.remove(className);
	}

	// TODO: See CompiledClassRecord!
	// Opening this URL's input stream will cause a very nasty error...
	// TODO: Support finding resources on the same class path as the script, ie need to know original source paths in this loader...
	@Override
	public URL findResource(final @NotNull String relativeUrl)
	{
		if (isClassResource(relativeUrl) && knownClassNames.contains(relativeUrlRepresentingAResourceOnTheClassPathToClassName(relativeUrl)))
		{
			try
			{
				return CompiledClassRecord.toUri(relativeUrlRepresentingAResourceOnTheClassPathToClassName(relativeUrl)).toURL();
			}
			catch (MalformedURLException e)
			{
				throw new IllegalStateException("Should never happen", e);
			}
		}
		return super.findResource(relativeUrl);
	}
}